﻿package kliment.controls.transformtool {
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	import kliment.display.draw.DrawStyle;
	import kliment.display.draw.shape.CircleShape;
	import kliment.display.draw.shape.RectangleShape;
	import kliment.display.draw.style.LineStyle;
	/**
	 * ...
	 * @author Kliment
	 *
	 * class name: kliment.controls.transformtool.TransformTool
	 */
	public class TransformTool {
		private var _target:DisplayObject;

		private var _toolHolder:Sprite = new Sprite();
		private var _toolRectangleHolder:Sprite = new Sprite();
		private var _toolSizeHolder:Sprite = new Sprite();

		private var _toolMatrix:Matrix = new Matrix();

		private var _mouseLoc:Point = new Point();
		private var _mouseOffset:Point = new Point();
		private var _innerMouseLoc:Point = new Point();
		private var _interactionStart:Point = new Point();
		private var _innerInteractionStart:Point = new Point();
		private var _interactionStartAngle:Number = 0;
		private var _interactionStartMatrix:Matrix = new Matrix();
		private var _innerRegistration:Point = new Point();
		private var _registration:Point = new Point();

		private var _maxScaleX:Number = Infinity;
		private var _maxScaleY:Number = Infinity;

		private var _constrainScale:Boolean = false;
		private var _constrainRotationAngle:Number = Math.PI/4; // default at 45 degrees
		private var _constrainRotation:Boolean = false;
		private var _boundsTopLeft:Point = new Point();
		private var _boundsTop:Point = new Point();
		private var _boundsTopRight:Point = new Point();
		private var _boundsRight:Point = new Point();
		private var _boundsBottomRight:Point = new Point();
		private var _boundsBottom:Point = new Point();
		private var _boundsBottomLeft:Point = new Point();
		private var _boundsLeft:Point = new Point();
		private var _boundsCenter:Point = new Point();

		private var _scaleTopLeftControl:TransformToolControl;
		private var _scaleTopControl:TransformToolControl;
		private var _scaleTopRightControl:TransformToolControl;
		private var _scaleRightControl:TransformToolControl;
		private var _scaleBottomRightControl:TransformToolControl;
		private var _scaleBottomControl:TransformToolControl;
		private var _scaleBottomLeftControl:TransformToolControl;
		private var _scaleLeftControl:TransformToolControl;

		private var _targetBounds:Rectangle;
		private var _targetDrawBounds:Rectangle;

		public var gapBounds:Number = 0;

		public function TransformTool(parent:Sprite) {
			_toolHolder.addChild(_toolRectangleHolder);
			_toolHolder.addChild(_toolSizeHolder);

			_scaleTopLeftControl = new TransformToolControl();
			_scaleTopControl = new TransformToolControl();
			_scaleTopRightControl = new TransformToolControl();
			_scaleRightControl = new TransformToolControl();
			_scaleBottomRightControl = new TransformToolControl();
			_scaleBottomControl = new TransformToolControl();
			_scaleBottomLeftControl = new TransformToolControl();
			_scaleLeftControl = new TransformToolControl();

			_toolSizeHolder.addChild(_scaleTopLeftControl);
			_toolSizeHolder.addChild(_scaleTopControl);
			_toolSizeHolder.addChild(_scaleTopRightControl);
			_toolSizeHolder.addChild(_scaleRightControl);
			_toolSizeHolder.addChild(_scaleBottomRightControl);
			_toolSizeHolder.addChild(_scaleBottomControl);
			_toolSizeHolder.addChild(_scaleBottomLeftControl);
			_toolSizeHolder.addChild(_scaleLeftControl);

			_scaleTopLeftControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleTopControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleTopRightControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleRightControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleBottomRightControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleBottomControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleBottomLeftControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
			_scaleLeftControl.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);

			_toolHolder.visible = false;
			parent.addChild(_toolHolder);
		}

		private function _mouseDownHandler(event:MouseEvent):void {
			var eventTarget:TransformToolControl = event.target as TransformToolControl;

			eventTarget.addEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
			eventTarget.addEventListener(MouseEvent.MOUSE_UP, _mouseUpHandler);
		}

		private function _mouseMoveHandler(event:MouseEvent):void {

		}

		private function _mouseUpHandler(event:MouseEvent):void {
			var eventTarget:TransformToolControl = event.target as TransformToolControl;

			eventTarget.removeEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
			eventTarget.removeEventListener(MouseEvent.MOUSE_UP, _mouseUpHandler);
		}

		public function get target():DisplayObject { return _target; }
		public function set target(value:DisplayObject):void {
			_target = value;
			update();
		}

		/**
		 * Control Interaction.  Scales the tool along the X axis
		 */
		public function scaleXInteraction():void {

			// get distortion offset vertical movement
			var distortH:Point = _distortOffset(new Point(_innerMouseLoc.x, _innerInteractionStart.y), _innerInteractionStart.x - _innerRegistration.x);

			// update the matrix for vertical scale
			_toolMatrix.a += distortH.x;
			_toolMatrix.b += distortH.y;
			_completeInteraction(true);
		}

		/**
		 * Control Interaction.  Scales the tool along the Y axis
		 */
		public function scaleYInteraction():void {
			// get distortion offset vertical movement
			var distortV:Point = _distortOffset(new Point(_innerInteractionStart.x, _innerMouseLoc.y), _innerInteractionStart.y - _innerRegistration.y);

			// update the matrix for vertical scale
			_toolMatrix.c += distortV.x;
			_toolMatrix.d += distortV.y;
			_completeInteraction(true);
		}

		/**
		 * Control Interaction.  Scales the tool along both the X and Y axes
		 */
		public function scaleBothInteraction():void {
			// mouse reference, may change from innerMouseLoc if constraining
			var innerMouseRef:Point = _innerMouseLoc.clone();

			if (_constrainScale) {

				// how much the mouse has moved from starting the interaction
				var moved:Point = _innerMouseLoc.subtract(_innerInteractionStart);

				// the relationship of the start location to the registration point
				var regOffset:Point = _innerInteractionStart.subtract(_innerRegistration);

				// find the ratios between movement and the registration offset
				var ratioH:Number = regOffset.x ? moved.x/regOffset.x : 0;
				var ratioV:Number = regOffset.y ? moved.y/regOffset.y : 0;

				// have the larger of the movement distances brought down
				// based on the lowest ratio to fit the registration offset
				if (ratioH > ratioV) {
					innerMouseRef.x = _innerInteractionStart.x + regOffset.x * ratioV;
				} else {
					innerMouseRef.y = _innerInteractionStart.y + regOffset.y * ratioH;
				}
			}

			// get distortion offsets for both vertical and horizontal movements
			var distortH:Point = _distortOffset(new Point(innerMouseRef.x, _innerInteractionStart.y), _innerInteractionStart.x - _innerRegistration.x);
			var distortV:Point = _distortOffset(new Point(_innerInteractionStart.x, innerMouseRef.y), _innerInteractionStart.y - _innerRegistration.y);

			// update the matrix for both scales
			_toolMatrix.a += distortH.x;
			_toolMatrix.b += distortH.y;
			_toolMatrix.c += distortV.x;
			_toolMatrix.d += distortV.y;
			_completeInteraction(true);
		}

		private function _distortOffset(offset:Point, regDiff:Number):Point {
			// get changes in matrix combinations based on targetBounds
			var ratioH:Number = regDiff ? _targetBounds.width/regDiff : 0;
			var ratioV:Number = regDiff ? _targetBounds.height/regDiff : 0;
			offset = _interactionStartMatrix.transformPoint(offset).subtract(_interactionStart);
			offset.x *= _targetBounds.width ? ratioH/_targetBounds.width : 0;
			offset.y *= _targetBounds.height ? ratioV/_targetBounds.height : 0;
			return offset;
		}

		private function _completeInteraction(offsetReg:Boolean = false):void {
			_enforceLimits();
			if (offsetReg) {
				// offset of registration to have transformations based around
				// custom registration point
				var offset:Point = _registration.subtract(_toolMatrix.transformPoint(_innerRegistration));
				_toolMatrix.tx += offset.x;
				_toolMatrix.ty += offset.y;
			}
			_updateBounds();
		}

		private function _enforceLimits():void {

			var currScale:Number;
			var angle:Number;
			var enforced:Boolean = false;

			// use global matrix
			var _globalMatrix:Matrix = _toolMatrix.clone();
			//_globalMatrix.concat(transform.concatenatedMatrix);

			// check current scale in X
			currScale = Math.sqrt(_globalMatrix.a * _globalMatrix.a + _globalMatrix.b * _globalMatrix.b);
			if (currScale > _maxScaleX) {
				// set scaleX to no greater than _maxScaleX
				angle = Math.atan2(_globalMatrix.b, _globalMatrix.a);
				_globalMatrix.a = Math.cos(angle) * _maxScaleX;
				_globalMatrix.b = Math.sin(angle) * _maxScaleX;
				enforced = true;
			}

			// check current scale in Y
			currScale = Math.sqrt(_globalMatrix.c * _globalMatrix.c + _globalMatrix.d * _globalMatrix.d);
			if (currScale > _maxScaleY) {
				// set scaleY to no greater than _maxScaleY
				angle= Math.atan2(_globalMatrix.c, _globalMatrix.d);
				_globalMatrix.d = Math.cos(angle) * _maxScaleY;
				_globalMatrix.c = Math.sin(angle) * _maxScaleY;
				enforced = true;
			}


			// if scale was enforced, apply to _toolMatrix
			if (enforced) {
				_toolMatrix = _globalMatrix;
				/*var current:Matrix = transform.concatenatedMatrix;
				current.invert();
				_toolMatrix.concat(current);*/
			}
		}

		public function update():void {

			_toolMatrix = _target.transform.matrix.clone();

			_updateBounds();

			_drawBounds();
		}

		private function _updateBounds():void {
			if (_target) {
				_toolHolder.visible = true;
				_targetBounds = _target.getBounds(_target);

				_targetDrawBounds = _targetBounds.clone();
				_targetDrawBounds.x -= gapBounds - 1;
				_targetDrawBounds.y -= gapBounds - 1;
				_targetDrawBounds.width += gapBounds * 2;
				_targetDrawBounds.height += gapBounds * 2;

				_boundsTopLeft = _toolMatrix.transformPoint(new Point(_targetDrawBounds.left, _targetDrawBounds.top));
				_boundsTopRight = _toolMatrix.transformPoint(new Point(_targetDrawBounds.right, _targetDrawBounds.top));
				_boundsBottomRight = _toolMatrix.transformPoint(new Point(_targetDrawBounds.right, _targetDrawBounds.bottom));
				_boundsBottomLeft = _toolMatrix.transformPoint(new Point(_targetDrawBounds.left, _targetDrawBounds.bottom));
				_boundsTop = Point.interpolate(_boundsTopLeft, _boundsTopRight, .5);
				_boundsRight = Point.interpolate(_boundsTopRight, _boundsBottomRight, .5);
				_boundsBottom = Point.interpolate(_boundsBottomRight, _boundsBottomLeft, .5);
				_boundsLeft = Point.interpolate(_boundsBottomLeft, _boundsTopLeft, .5);
				_boundsCenter = Point.interpolate(_boundsTopLeft, _boundsBottomRight, .5);

				_boundsTop.x = Math.round(_boundsTop.x);
				_boundsTop.y = Math.round(_boundsTop.y);
				_boundsRight.x = Math.round(_boundsRight.x);
				_boundsRight.y = Math.round(_boundsRight.y);
				_boundsBottom.x = Math.round(_boundsBottom.x);
				_boundsBottom.y = Math.round(_boundsBottom.y);
				_boundsLeft.x = Math.round(_boundsLeft.x);
				_boundsLeft.y = Math.round(_boundsLeft.y);
			}
			else
			{
				_toolHolder.visible = false;
			}
		}

		protected function _drawBounds():void {

			_toolRectangleHolder.graphics.clear();
			_toolRectangleHolder.graphics.beginFill(0x000000, 0);
			_toolRectangleHolder.graphics.lineStyle(1, 0, 1, true);
			_toolRectangleHolder.graphics.moveTo(_boundsTopLeft.x, _boundsTopLeft.y);
			_toolRectangleHolder.graphics.lineTo(_boundsTopRight.x, _boundsTopRight.y);
			_toolRectangleHolder.graphics.lineTo(_boundsBottomRight.x, _boundsBottomRight.y);
			_toolRectangleHolder.graphics.lineTo(_boundsBottomLeft.x, _boundsBottomLeft.y);
			_toolRectangleHolder.graphics.lineTo(_boundsTopLeft.x, _boundsTopLeft.y);
			_toolRectangleHolder.graphics.endFill();

			_setScaleControlPosition(_scaleTopLeftControl, _boundsTopLeft);
			_setScaleControlPosition(_scaleTopRightControl, _boundsTopRight);
			_setScaleControlPosition(_scaleBottomRightControl, _boundsBottomRight);
			_setScaleControlPosition(_scaleBottomLeftControl, _boundsBottomLeft);
			_setScaleControlPosition(_scaleTopControl, _boundsTop);
			_setScaleControlPosition(_scaleBottomControl, _boundsBottom);
			_setScaleControlPosition(_scaleLeftControl, _boundsLeft);
			_setScaleControlPosition(_scaleRightControl, _boundsRight);
		}

		private function _setScaleControlPosition(target:TransformToolControl, value:Point):void {
			target.x = value.x - target.width * .5;
			target.y = value.y - target.height * .5;
		}

		public function remove():void {
			if (_toolHolder.parent)
				_toolHolder.parent.removeChild(_toolHolder);
		}
	}

}